﻿using Digitalmes.Application.Configuration;
using Digitalmes.Application.Mes;
using Digitalmes.Application.Utils;
using Digitalmes.Common;
using Digitalmes.Common.Utils;
using Digitalmes.Domain;
using Digitalmes.Domain.Enums;
using Digitalmes.Domain.Mes;
using Digitalmes.Scada.Models;
using Digitalmes.Sugar;

namespace Digitalmes.Scada.Services.Impl;

/// <summary>
/// 产品进站服务
/// </summary>
internal sealed class EntryService : IEntryService, ITransientDependency
{
    private readonly SugarRepository<ProdTransSn> _transSnRepo;
    private readonly SugarRepository<ProdTransSnRecord> _tranSnRecordRepo;
    private readonly SugarRepository<ProdInbound> _inboundRepo;
    private readonly SugarRepository<MdFormualCtl> _formualRepo;
    private readonly SugarRepository<ProcRoutingProduct> _routingProductRepo;
    private readonly SugarRepository<MdStation> _stationRepo;
    private readonly ProcessQueryService _processQuery;
    private readonly WoIncrementService _woIncrementService;
    private readonly ReworkRelaunchService _reworkRelaunchService;

    private readonly ApplicationConfig _appConfig;

    public EntryService(
        SugarRepository<ProdTransSn> transSnRepo,
        SugarRepository<ProdTransSnRecord> tranSnRecordRepo,
        SugarRepository<ProdInbound> inboundRepo,
        SugarRepository<MdFormualCtl> formualRepo,
        SugarRepository<ProcRoutingProduct> routingProductRepo,
        SugarRepository<MdStation> stationRepo,
        ProcessQueryService processQuery,
        WoIncrementService woIncrementService,
        ReworkRelaunchService reworkRelaunchService,
        IOptions<ApplicationConfig> appConfigOptions)
    {
        _transSnRepo = transSnRepo;
        _tranSnRecordRepo = tranSnRecordRepo;
        _inboundRepo = inboundRepo;
        _formualRepo = formualRepo;
        _routingProductRepo = routingProductRepo;
        _stationRepo = stationRepo;
        _processQuery = processQuery;
        _woIncrementService = woIncrementService;
        _reworkRelaunchService = reworkRelaunchService;
        _appConfig = appConfigOptions.Value;
    }

    public async Task<HandleResult> EntryAsync(EntryInput input)
    {
        // 检测是否为线外站
        var station = await _stationRepo.GetFirstAsync(s => s.LineCode == input.LineCode && s.OpCode == input.OpCode);
        if (station == null)
        {
            return HandleResult.Error(ErrCode.E1103);
        }

        // 线外站没有工艺路线和工单，走线外流程。
        if (station.Owner == OpOwnerEnum.Outside)
        {
            // TODO: 线外返工站，走返工流程

            // 线外站（非返工），走线外流程
            return await OutsideLoadingAsync(station, input);
        }

        // 先查看 SN 流转状态，若有数据，表示 SN 非首次上线。
        var transSn = await _transSnRepo.GetFirstAsync(s => s.Sn == input.SN && s.CurLineCode == input.LineCode);
        if (transSn != null)
        {
            // 产品在当前工位还未出站，不处理后续动作（屏蔽在当前工位重复进站）。
            if (transSn.CurOpCode == input.OpCode && transSn.Stage == SnStageEnum.In)
            {
                // 更新产品进站时间
                transSn.InboundTime = DateTime.Now;
                await _transSnRepo.UpdateAsync(transSn, s => new { s.InboundTime });

                return HandleResult.Ok();
            }

            // 线上站（可能还是首站，但已上过线）
            var (ok, result) = await LoadingAsync(station, transSn, input);
            if (!ok)
            {
                return result!;
            }
        }
        else if (transSn == null)
        {
            // 线上站，首次上线处理
            var (ok, result) = await FirstLoadingAsync(station, input);
            if (!ok)
            {
                return result!;
            }
        }

        return HandleResult.Ok();
    }

    /// <summary>
    /// 线上站，首站首次上件处理。
    /// </summary>
    /// <remarks>在首站且是第一次上线操作。</remarks>
    /// <returns></returns>
    private async Task<(bool ok, HandleResult? result)> FirstLoadingAsync(MdStation station, EntryInput input)
    {
        MdItem? product0 = null;

        // 检查是采用系统下发工单还是固定工单
        bool isFixedWorkOrder = _appConfig.WorkOrder.IsFixed;
        if (isFixedWorkOrder)
        {
            // 根据 "产线" 和 "配方号"，通过 "配方管控" 和 "产品制程" 查找对应的 "工艺路线"
            var formualCtl = await _formualRepo.AsQueryable()
                .Includes(s => s.Product)
                .FirstAsync(s => s.FormualNo == input.FormualNo);

            product0 = formualCtl?.Product;
            if (product0 == null)
            {
                return (false, HandleResult.Error(ErrCode.E1104));
            }
        }

        // 根据产品找到找到当前对应工单
        var workOrder = await _woIncrementService.GetCurrentWoAsync(product0?.Id);
        if (workOrder == null)
        {
            return (false, HandleResult.Error(ErrCode.E1105));
        }

        var product = workOrder.Product!;

        // 校验 SN 条码规则
        if (!RuleCheckerUtil.CheckRules(input.SN, product.Rules, AppConsts.BarcodeRulePlaceholder, true))
        {
            return (false, HandleResult.Error(ErrCode.E1106));
        }

        // 找到对应的工艺路线
        var routingProduct = await _routingProductRepo.AsQueryable()
            .Includes(s => s.Routing)
            .FirstAsync(s => s.ProductId == product.Id && s.Routing!.LineCode == input.LineCode);
        if (routingProduct == null)
        {
            return (false, HandleResult.Error(ErrCode.E1107));
        }

        var routing = routingProduct.Routing!;

        // TODO：若工艺路线中，前一系列启用工序都为可选时，可允许零件在这些工序中任何一序上线（后面校验逻辑要处理）。
        // 这些可选项中，若若后一工序先完成，再做前一工序，应该卡控住。

        // 校验当前站是否为首站（新产品必须从首站上线）
        var isFirst = await _processQuery.IsFirst2Async(routing.RoutingCode, input.OpCode);
        if (!isFirst)
        {
            // 非首站，不能上线
            return (false, HandleResult.Error(ErrCode.E1108));
        }

        var curProcCtl = await _processQuery.GetRoutingItem2Async(routing.RoutingCode, input.OpCode);
        if (curProcCtl == null)
        {
            return (false, HandleResult.Error(ErrCode.E1109));
        }

        // 新增SN流转状态
        ProdTransSn transSn = new()
        {
            Sn = input.SN,
            ProductId = product.Id,
            ProductCode = product.ItemCode,
            Wo = workOrder?.Wo ?? string.Empty, // 没有工单设为空
            CurLineId = routing.LineId,
            CurLineCode = input.LineCode,
            CurOpId = station.Id,
            CurOpCode = input.OpCode,
            CurProcId = curProcCtl!.ProcessId,
            CurProcCode = curProcCtl!.ProcCode,
            CurRoutingId = curProcCtl.RoutingId,
            CurRoutingCode = routing.RoutingCode,
            InboundTime = DateTime.Now,
            OnlineTime = DateTime.Now,
            Stage = SnStageEnum.In,
            Status = SnStatusEnum.OK,
        };
        await _transSnRepo.InsertAsync(transSn);

        // 新增进站数据
        ProdInbound inbound = new()
        {
            LineId = transSn.CurLineId,
            LineCode = transSn.CurLineCode,
            OpId = transSn.CurOpId,
            OpCode = transSn.CurOpCode,
            ProcId = transSn.CurProcId,
            ProcCode = transSn.CurProcCode,
            Sn = transSn.Sn,
            ProductCode = transSn.ProductCode,
            Wo = transSn.Wo,
            FormualNo = input.FormualNo,
            Rfid = input.Rfid,
            Version = Unique.NextId(),
            InboundTime = DateTime.Now,
        };
        await _inboundRepo.InsertAsync(inbound);

        // 更改工单状态
        await _woIncrementService.ProductOnlineIncrementAsync(workOrder!);

        return (true, null);
    }

    /// <summary>
    /// 线上站，非首站首次上件（可能是首站），且已出站。
    /// </summary>
    /// <remarks>可能是首站再次上线，但并非首次。</remarks>
    /// <returns></returns>
    private async Task<(bool ok, HandleResult? result)> LoadingAsync(MdStation station, ProdTransSn transSn, EntryInput input)
    {
        // 若已完工下线且为非返工流程，不允许再上线
        if (transSn.IsCompelted && !transSn.IsNeedRework())
        {
            return (false, HandleResult.Error(ErrCode.E1110));
        }

        // 非返工流程
        var curProcCtl = await _processQuery.GetRoutingItem2Async(transSn.CurRoutingCode, input.OpCode); // 当前工序配置项
        if (curProcCtl == null)
        {
            return (false, HandleResult.Error(ErrCode.E1109));
        }

        var curProcess = curProcCtl.Process;

        // 返工流程
        var (cando, isReworkProc, isReworkRelaunchProc) = (false, false, false);
        if (transSn.IsNeedRework())
        {
            (cando, isReworkProc, isReworkRelaunchProc, _) = await _reworkRelaunchService.CheckReworkAsync(transSn, curProcess.ProcCode);
            if (!cando)
            {
                return (false, HandleResult.Error(ErrCode.E1111));
            }

            // TODO: 若是在线返工，直接放行。
        }

        #region 策略校验

        // 若未设置允许在当前工位重复进站的策略
        // 若允许重复进站，无论产品 NG 或 OK，都允许再次进站作业。
        if (!curProcCtl.PolicyCheckProcess.HasFlag(PolicyCheckProcessEnum.AllowedEntryRepeat))
        {
            var (ok, result) = await PolicyNotAllowedEntryRepeatAsync(curProcess.Id, transSn, input, isReworkProc);
            if (!ok)
            {
                return (false, result);
            }
        }

        // 校验上一个工序策略（包括调序），首站和反复进站都不会校验上一工序
        if (curProcCtl.PolicyCheckPrevProc is not PolicyCheckPrevProcEnum.None)
        {
            var (ok2, result2) = await PolicyCheckPrevProcAsync(curProcCtl.PolicyCheckPrevProc,
                    curProcess.ProcCode, transSn, isReworkRelaunchProc);
            if (!ok2)
            {
                return (false, result2);
            }
        }

        #endregion

        // 更新SN流转状态（允许重复进站）
        transSn.CurOpId = station.Id;
        transSn.CurOpCode = input.OpCode;
        transSn.CurProcId = curProcess.Id;
        transSn.CurProcCode = curProcess.ProcCode;
        transSn.InboundTime = DateTime.Now;
        transSn.Stage = SnStageEnum.In;
        transSn.Status = SnStatusEnum.OK;
        await _transSnRepo.UpdateAsync(transSn);

        // 新增进站数据
        ProdInbound inbound = new()
        {
            LineId = transSn.CurLineId,
            LineCode = transSn.CurLineCode,
            OpId = transSn.CurOpId,
            OpCode = transSn.CurOpCode,
            ProcId = transSn.CurProcId,
            ProcCode = transSn.CurProcCode,
            Sn = transSn.Sn,
            ProductCode = transSn.ProductCode,
            Wo = transSn.Wo,
            FormualNo = input.FormualNo,
            Rfid = input.Rfid,
            InboundTime = DateTime.Now,
            Version = Unique.NextId(),
        };
        await _inboundRepo.InsertAsync(inbound);

        return (true, null);
    }

    /// <summary>
    /// 线外站，上件
    /// </summary>
    private async Task<HandleResult> OutsideLoadingAsync(MdStation station, EntryInput input)
    {
        // 注：线外站允许重复上线

        // 配方找到对应产品
        var formualCtl = await _formualRepo.AsQueryable()
                .Includes(s => s.Product)
                .FirstAsync(s => s.FormualNo == input.FormualNo);
        var product = formualCtl?.Product;
        if (product == null)
        {
            return HandleResult.Error(ErrCode.E1104);
        }

        // 校验 SN 条码规则
        if (!RuleCheckerUtil.CheckRules(input.SN, product.Rules, AppConsts.BarcodeRulePlaceholder, true))
        {
            return HandleResult.Error(ErrCode.E1106);
        }

        // TODO: 线外站，找到可能对应的工序
        //SqlFunc.JsonArrayAny(it.Json, "a")

        // 新增SN流转状态
        ProdTransSn transSn = new()
        {
            Sn = input.SN,
            ProductId = product.Id,
            ProductCode = product.ItemCode,
            Wo = string.Empty,
            CurLineId = station.LineId,
            CurLineCode = station.LineCode,
            CurOpId = station.Id,
            CurOpCode = station.OpCode,
            CurProcId = 0,
            CurProcCode = string.Empty,
            CurRoutingId = 0, // 线外站没有工艺路线
            CurRoutingCode = string.Empty,
            InboundTime = DateTime.Now,
            OnlineTime = DateTime.Now,
            Stage = SnStageEnum.In,
            Status = SnStatusEnum.OK,
        };
        await _transSnRepo.InsertAsync(transSn);

        // 新增进站数据
        ProdInbound inbound = new()
        {
            LineId = transSn.CurLineId,
            LineCode = transSn.CurLineCode,
            OpId = transSn.CurOpId,
            OpCode = transSn.CurOpCode,
            ProcId = transSn.CurProcId,
            ProcCode = transSn.CurProcCode,
            Sn = transSn.Sn,
            ProductCode = transSn.ProductCode,
            Wo = transSn.Wo,
            FormualNo = input.FormualNo,
            Rfid = input.Rfid,
            InboundTime = DateTime.Now,
            Version = Unique.NextId(),
        };
        await _inboundRepo.InsertAsync(inbound);

        return HandleResult.Ok();
    }

    /// <summary>
    /// 不允许重复进站策略
    /// </summary>
    private async Task<(bool ok, HandleResult? result)> PolicyNotAllowedEntryRepeatAsync(long curProcessId,
        ProdTransSn transSn, EntryInput input, bool isReworkOrRelaunchProc)
    {
        // 不允许重复进站逻辑：
        // 查看产品当前状态，若产品目前处于当前工位（已出站），不允许作业。
        if (transSn.CurProcId == curProcessId && transSn.Stage == SnStageEnum.Out)
        {
            return (false, HandleResult.Error(ErrCode.E1112));
        }

        // 检测历史记录中是否有在此工序过站（分返工筛选）
        // 若是在线返工件，检查此工序是否已做过返工作业
        var isExist = await _tranSnRecordRepo.AsQueryable()
            .Where(s => s.Sn == input.SN && s.ProcId == curProcessId)
            .WhereIF(isReworkOrRelaunchProc, s => s.FlowType == FlowTypeEnum.Rework && s.ReworkDoc == transSn.ReworkDoc) // 返工条件
            .AnyAsync();
        if (isExist)
        {
            return (false, HandleResult.Error(ErrCode.E1112));
        }

        return (true, default);
    }

    /// <summary>
    /// 上一工序过站策略校验
    /// </summary>
    /// <returns></returns>
    private async Task<(bool ok, HandleResult? result)> PolicyCheckPrevProcAsync(PolicyCheckPrevProcEnum policy,
        string curProcCode, ProdTransSn transSn, bool isReworkRelaunchProc)
    {
        if (policy == PolicyCheckPrevProcEnum.None)
        {
            return (true, null);
        }

        // 若是返修重上线工序，不用校验（返修后可以在任意工位重上线作业）
        if (isReworkRelaunchProc)
        {
            return (true, null);
        }

        // 首站不用校验上一工序
        var isFirst = await _processQuery.IsFirstAsync(transSn.CurRoutingCode, curProcCode);
        if (isFirst)
        {
            return (true, null);
        }

        // 校验是否是存在跳站
        // 查找 SN 在该工序的前一工序最后过站记录
        var (_, prevProcCtl) = await _processQuery.PrevProcessCtlAsync(transSn.CurRoutingCode, curProcCode);
        var tranSnRecord = await _tranSnRecordRepo.GetLastAsync(s => s.Sn == transSn.Sn && s.ProcId == prevProcCtl!.ProcessId);

        // 若是可选工序，且该工序没有作业时继续查找前一工序，依次类推（首站也可能是可选工序）
        while (tranSnRecord == null && prevProcCtl?.CtlAttr == ProcCtlAttrEnum.Optional)
        {
            (_, prevProcCtl) = await _processQuery.PrevProcessCtlAsync(transSn.CurRoutingCode, prevProcCtl.ProcCode);
            // 此处判断上一管控工序是否存在（递归到首站且未过站，管控工序为null）
            if (prevProcCtl is null)
            {
                return (false, HandleResult.Error(ErrCode.E1113));
            }

            tranSnRecord = await _tranSnRecordRepo.GetLastAsync(s => s.Sn == transSn.Sn && s.ProcId == prevProcCtl.ProcessId);
        }

        // 若该工序选项是 "Must"，但没找到过站结果时，存在判定跳序行为
        if (tranSnRecord == null && prevProcCtl?.CtlAttr == ProcCtlAttrEnum.Must)
        {
            return (false, HandleResult.Error(ErrCode.E1113));
        }

        // 检查工序过站结果
        if (tranSnRecord != null && policy == PolicyCheckPrevProcEnum.WorkedAndOK)
        {
            if (tranSnRecord.PassResult is PassResultEnum.NG or PassResultEnum.MandatoryNG)
            {
                return (false, HandleResult.Error(ErrCode.E1114));
            }
        }

        return (true, null);
    }
}
